package cn.webank.framework.concurrent;

import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.springframework.util.Assert;

import cn.webank.framework.concurrent.integration.dao.DistributedLockDAO;
import cn.webank.framework.concurrent.model.LockControl;
import cn.webank.framework.exception.SysException;

public class DistributedDBLock implements DistributedLock {

	private final static Random r = new Random();

	private DistributedLockDAO dao;
	private long expireTimeout;
	private TimeUnit expireTimeUnit;
	private String moduleId;

	public DistributedDBLock(String moduleId, DistributedLockDAO dao, long expireTimeout, TimeUnit expireTimeUnit) {
		this.dao = dao;
		this.expireTimeout = expireTimeout;
		this.expireTimeUnit = expireTimeUnit;
		this.moduleId = moduleId;
	}

	@Override
	public boolean tryLock(long tryLockTimeout, TimeUnit tryLockTimeUnit) {

		Assert.isTrue(tryLockTimeout > 0, "tryLockTimeout must lager than zero");
		Assert.isTrue(tryLockTimeout > 0, "expireTimeout must lager than zero");
		Assert.notNull(moduleId, "moduleId is null");

		long start = System.currentTimeMillis();
		long millis = tryLockTimeUnit.toMillis(tryLockTimeout);

		do {
			try {
				LockControl lockControl = dao.queryOldValueByModuleId(moduleId);
				if (lockControl != null && lockControl.getModuleId() != null
						&& lockControl.getVersion().equalsIgnoreCase("0")) {
					Map<String, Object> paramMap = new HashMap<String, Object>();
					paramMap.put("newValue", "1");
					paramMap.put("modifiedTime", new Date());
					paramMap.put("moduleId", moduleId);
					paramMap.put("oldValue", "0");
					int updateNewValue = dao.updateNewValue(paramMap);
					if (updateNewValue > 0)
						return true;
				} else if ((lockControl != null) && (lockControl.getModuleId() != null)
						&& (lockControl.getVersion().compareTo("0") > 0)
						&& ((System.currentTimeMillis() - lockControl.getModifiedTime().getTime()) > expireTimeUnit
								.toMillis(expireTimeout))) {

					Map<String, Object> paramMap = new HashMap<String, Object>();
					paramMap.put("newValue", "" + (Integer.parseInt(lockControl.getVersion()) + 1));
					paramMap.put("modifiedTime", new Date());
					paramMap.put("moduleId", moduleId);
					paramMap.put("oldValue", lockControl.getVersion());
					int updateNewValue = dao.updateNewValue(paramMap);
					if (updateNewValue > 0)
						return true;

				}

				int nextInt = Math.abs(r.nextInt(100));
				Thread.sleep(nextInt);
			} catch (SQLException e) {
				throw new SysException("DistributedLockDAO error", e);
			} catch (InterruptedException e) {
				throw new SysException("InterruptedException error", e);
			}

		} while ((System.currentTimeMillis() - start) < millis);

		return false;

	}

	@Override
	public void unlock() {

		try {
			LockControl lockControl = dao.lockOldValueByModuleId(moduleId);
			if (lockControl != null && lockControl.getModuleId() != null && (!lockControl.getVersion().equals("0"))) {
				Map<String, Object> paramMap = new HashMap<String, Object>();
				paramMap.put("newValue", "0");
				paramMap.put("modifiedTime", new Date());
				paramMap.put("moduleId", moduleId);
				paramMap.put("oldValue", lockControl.getVersion());
				dao.updateNewValue(paramMap);
			}
		} catch (SQLException e) {
			throw new SysException("DistributedLockDAO error", e);
		}

	}

}
